#!/bin/sh

CACHE_ROOT="${CACHE_ROOT:-/srv/cache}"

# When using some options, you must follow the order specified in the usage below.
# usage: git_clone_update https://github.com/pmem/valgrind.git [dir] [--branch master] [--recursive]
git_clone_update()
{
	local url=$1
	local dir
	local branch=master
	shift

	if [ -n "$1" -a "$1" = "${1#-}" ]; then
		dir="$1"
		shift
	else
		dir=$(basename $url .git)
	fi

	if [ "$1" = "--branch" ] && [ -n "$2" ]; then
		branch=$2
	fi

	local git="timeout 60m git"

	if [ -d $dir/.git ]; then
		(
			cd $dir || exit
			while [ $# != 0 ]; do
				if [ "$1" = "--recursive" ]; then
					git submodule update --init --recursive
					break
				fi
				shift
			done
			for retry in 1 2
			do
				echo \
				$git remote update origin
				$git remote update origin 2>&1 ||
				$git remote update origin 2>&1

				echo \
				$git checkout -q origin/$branch
				$git checkout -q origin/$branch 2>&1 && break
			done
		)
	else
		rm -fr "$dir" 2>/dev/null
		echo \
		$git clone -q "$@" $url $dir
		$git clone -q "$@" $url $dir 2>&1 ||
		$git clone -q "$@" $url $dir 2>&1 ||
		$git clone -q "$@" $url $dir
	fi
}

# Parse a URL into a cache directory path
# returns var $cache_dir
url2cache_dir() {
    local url="$1"
    [ -z "$url" ] && return 1

    if [ "$url" != "${url#*..}" ]; then
        echo "Error: URL contains '..', potential security risk. Aborting." >&2
        return 1
    fi

    # Return nil if URL lacks protocol prefix
    if ! echo "$url" | grep -q -E '://|^git@|^git\+'; then
        return 1
    fi

    # Normalize URL by removing protocol variants and authentication
    url="${url##*::}"
    url="${url%%::*}"
    local uri=$(echo "$url" | sed -E \
        -e 's%^(git\+|git://|ssh://|https?://|ftp://|svn://|bzr://|hg://)%%' \
        -e 's|^git@||' \
        -e 's|//+|/|g' \
        -e 's|:[0-9]+/|/|' \
        -e 's|\?.*$||' \
        -e 's|#.*$||' \
        -e 's|%20|_|' \
        -e 's|\.git$||' \
        -e 's|\{.*?\}||'
    )

    # Handle SSH-style URLs (git@host:path)
    if echo "$uri" | grep -q '^[^/]*:[^/]'; then
        uri=$(echo "$uri" | sed 's|:|/|')
    fi

    # Split into host and path components
    local host=$(echo "$uri" | cut -d '/' -f 1)
    local path=$(echo "$uri" | cut -d '/' -f 2-)

    # Known Git services
    local git_services="
    anongit.kde.org
    bitbucket.org
    cran.r-project.org
    gitee.com
    github.com
    repo.or.cz
    salsa.debian.org
    sourceforge.net
    www.gaia-gis.it"

    # File extensions indicating archives
    local archive_exts=".tar.gz .tgz .tar.bz2 .tar.xz .tar.Z .zip
        .c .h .cpp .patch .diff
        .gem .rpm .deb
        .sig .asc .jar .so .a"

    # Git detection logic
    is_git=0
    if echo "$host" | grep -q -E '^(git|code\.)' || \
        echo "$git_services" | grep -q -F "$host" || \
        echo "$url" | grep -q -E -e '^(git://|git\+|git@)' -e '\.git$'; then
            # Exclude common archive patterns
            if ! echo "$path" | grep -q -E '/archive/|/releases/download/|/downloads/|/snapshot/' && \
                ! echo "$archive_exts" | grep -q -F "${path##*.}"; then
                            is_git=1
            fi
    fi

    # Directory level calculation
    local dir_level=$(echo "$uri" | tr -cd '/' | wc -c)
    dir_level=$((dir_level + 1))
    [ "$dir_level" -gt 9 ] && dir_level=9

    # Special handling for package registries
    if echo "$host" | grep -q -E 'pypi|rubygems|npmjs' || \
        echo "$path" | grep -q -E '/packages/|/downloads/'; then
          is_git=0
    fi

    # Construct cache directory path
    local base_dir="$([ "$is_git" -eq 1 ] && echo "${dir_level}-git" || echo "0-http")"
    local cache_path=$(echo "$host/$path" | tr -s '/' | sed -E -e 's|[^a-zA-Z0-9_/.-]|_|g' -e 's|/$||' -e 's|.git$||')

    cache_dir="$base_dir/$cache_path"
}

# Extract the URL from the clone arguments
# returns var $git_url
extract_git_url() {
    git_url=""
    local skip_next=0

    for arg in "$@"; do
        if [ "$skip_next" -eq 1 ]; then
            skip_next=0
            continue
        fi
        if [ "$arg" = "--" ]; then
            shift
            git_url="$1"
            break
        elif [ "$(echo "$arg" | cut -c1)" = "-" ]; then
            case "$arg" in
                --branch|--depth|--config|--reference|--origin|-o|--template|--separate-git-dir|-c|--jobs|-j|--shallow-since|--shallow-exclude|--recurse-submodules|--server-option|--filter|--also-filter-submodules)
                    skip_next=1
                    ;;
                --*=*) ;;
                --*)   ;;
                -*)
                    local flag=$(echo "$arg" | cut -c2)
                    case "$flag" in
                        b|o|c|j) skip_next=1 ;;
                    esac
                    ;;
            esac
        elif [ "${arg#*://}" != "$arg" ]; then
            git_url="$arg"
            break
        fi
    done

    if [ -z "$git_url" ]; then
        # No warn, since the call can be "git clone -q /path/to/bare/git/dir"
        # echo "Error: URL not found in arguments." >&2
        return 1
    fi

    # returns var "$git_url"
}

# Update the cache repository using `git remote update`
git_update_cache() {
    cache_dir="$1"
    if [ ! -d "$cache_dir" ]; then
        mkdir -p "$(dirname "$cache_dir")" || return 1
        if ! command git clone --mirror "$url" "$cache_dir"; then
            echo "Error: Failed to clone repository into cache." >&2
            return 1
        fi
    else
        # mark referenced, to avoid being reclaimed
        touch "$cache_dir"
    fi

    command git -C "$cache_dir" remote update --prune
}

git_clone_cache() {
    local git_url
    local git_cache_dir
    local cache_dir
    local is_git

    mountpoint -q "$CACHE_ROOT" || return

    extract_git_url "$@" || return
    url2cache_dir "$git_url" || return
    [ "$is_git" -eq 1 ] || return
    git_cache_dir=$CACHE_ROOT/$cache_dir

    git_update_cache "$git_cache_dir" || return

    # Insert --reference into the clone arguments
    local new_args=""
    local found_url=0
    for arg in "$@"; do
        if [ "$arg" = "$git_url" ] && [ "$found_url" -eq 0 ]; then
            new_args="$new_args --reference $git_cache_dir"
            found_url=1
        fi
        new_args="$new_args $arg"
    done

    command git clone $new_args
}

# git wrapper to make use of CACHE_ROOT
git() {
    if [ "$1" = "clone" ]; then
        shift
        git_clone_cache "$@" || command git clone "$@"
    else
        command git "$@"
    fi
}

# vim:set ts=4 sw=4 et:
